package com.mvu.banana.tools;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

/**
 * Class description...
 *
 * @author mvu
 */
public class JavaClass {
  String name;
  private String _superClass = "";
  private List<String> _interfaces = new ArrayList<String>();
  ArrayList<JavaField> fields = new ArrayList<JavaField>();
  private ArrayList<JavaMethod> _methods = new ArrayList<JavaMethod>();
  private HashSet<String> _importedClasses = new HashSet<String>();
  String packageName;
  private ArrayList<JavaMethod> _constructors = new ArrayList<JavaMethod>();
  private List<String> _annotations = new ArrayList<String>();
  private boolean _isEnum;
  private List<String> _codes = new ArrayList<String>();
  boolean _isInterface;
  private String _identifier = "class";
  protected Object[] info;
  private String modifier;

  public void addCode(String code) {
    _codes.add(code);
  }

  public JavaClass() {
  }

  public JavaClass withName(String name) {
    int i = name.lastIndexOf('.');
    return withName(name.substring(0, i), name.substring(i + 1));
  }

  public JavaClass withName(String packageName, String name) {
    this.packageName = packageName;
    this.name = name;
    return this;
  }

  public JavaClass withSuperClass(Class superClass) {
    importClass(superClass);
    return withSuperClass(superClass.getSimpleName());
  }

  public JavaClass withSuperClass(Object superClass, String... classParameter) {
    String simpleName = importClass(superClass);
    StringBuilder builder = new StringBuilder(" extends ").append(simpleName);
    if (classParameter.length > 0) {
      String name = importClass(classParameter[0]);
      builder.append("<").append(name).append(">");
    }
    _superClass = builder.toString();
    return this;
  }

  public JavaClass withInterfaces(String... interfaces) {
    for (String intf : interfaces) {
      _interfaces.add(intf);
    }
    return this;
  }

  public JavaClass withInterfaces(JavaClass... interfaces) {
    for (JavaClass intf : interfaces) {
      importClass(intf);
      _interfaces.add(intf.name);
    }
    return this;
  }

  public String print() {
    StringBuilder builder = new StringBuilder();
    builder.append("package ").append(packageName).append(";");
    for (String importedClass : _importedClasses) {
      builder.append("\nimport ").append(importedClass).append(";");
    }
    builder.append("\n\n");
    for (String annotation : _annotations) {
      builder.append("@").append(annotation);
    }
    builder.append("\npublic ");
    if (modifier != null) {
      builder.append(modifier).append(" ");
    }
    builder.append(_identifier).append(" ").append(name)
            .append(_superClass);
    if (!_interfaces.isEmpty()) {
      Iterator<String> iterator = _interfaces.iterator();
      builder.append(" implements ").append(iterator.next());
      while (iterator.hasNext()) {
        builder.append(", ").append(iterator.next());
      }
    }
    builder.append("{");
    for (JavaField field : fields) {
      builder.append("\n").append(field.print());
    }
    for (JavaMethod constructor : _constructors) {
      builder.append(constructor.print());
    }
    for (JavaMethod method : _methods) {
      builder.append(method.print());
    }
    if (_isEnum) {
      builder.append("\n  ").append(name.toUpperCase());
      for (String code : _codes) {
        builder.append(", ").append(code.toUpperCase());
      }
    }
    builder.append("\n}");
    return builder.toString();
  }

  public void addField(String modifier, String name, Class type, String value) {
    JavaField field = new JavaField(this).withModifier(modifier)
            .withType(type)
            .withName(name)
            .withValue(value);
    fields.add(field);
  }

  public JavaField addField() {
    JavaField field = new JavaField(this);
    fields.add(field);
    return field;
  }

  public JavaMethod addMethod(Class returnType, Class typeParameter, String name, Class... paramTypes) {
    importClass(returnType);
    StringBuilder builder = new StringBuilder(returnType.getSimpleName());
    if (typeParameter != null) {
      importClass(typeParameter);
      builder.append("<").append(typeParameter.getSimpleName()).append(">");
    }
    return addMethod(false, builder.toString(), name, paramTypes);
  }

  public JavaMethod addMethod(boolean isStatic, String returnType, String name, Object... paramTypes) {
    JavaMethod javaMethod = new JavaMethod(this).withName(name)
            .asStatic(isStatic)
            .withReturnType(returnType)
            .withParameters(paramTypes);
    _methods.add(javaMethod);
    return javaMethod;
  }

  public JavaMethod addConstructor(Class... paramTypes) {
    JavaMethod constructor = new JavaMethod(this).withName(name).withParameters(paramTypes);
    _constructors.add(constructor);
    return constructor;
  }

  public String importClass(Object importedClass) {
    String packageName;
    String name;
    String simpleName;
    if (importedClass instanceof JavaType) {
      JavaType javaType = (JavaType) importedClass;
      packageName = javaType.packageName;
      if (packageName == null) {
        return javaType.elementName;
      }
      name = javaType.packageName + "." + javaType.elementName;
      simpleName = javaType.name;
      if (javaType.collectionName != null) {
        importClass(javaType.collectionName);
      }
    } else if (importedClass instanceof Class) {
      Class casted = (Class) importedClass;
      if (casted.isPrimitive()) {
        return casted.getSimpleName();
      }
      if (casted.isArray()) {
        casted = casted.getComponentType();
      }
      packageName = casted.getPackage().getName();
      name = casted.getName();
      simpleName = casted.getSimpleName();
    } else if (importedClass instanceof JavaClass) {
      JavaClass javaClass = (JavaClass) importedClass;
      name = javaClass.packageName + "." + javaClass.name;
      packageName = javaClass.packageName;
      simpleName = javaClass.name;
    } else if (importedClass instanceof String) {
      name = ((String) importedClass);
      int i = name.lastIndexOf(".");
      if (i == -1) {
        return name;
      }
      packageName = name.substring(0, i);
      simpleName = name.substring(i + 1);
    } else {
      throw new IllegalStateException("Uknown class: " + importedClass);
    }
    if (importedClass != null &&
            !"java.lang".equals(packageName) &&
            !this.packageName.equals(packageName)) {
      _importedClasses.add(name);
    }
    return simpleName;
  }

  public void writeToMain(boolean isOverride) {
    write("src/main/java", isOverride);
  }

  public void writeToTestFolder(boolean isOverride) {
    write("src/test/java", isOverride);
  }

  protected void write(String folder, boolean isOverride) {
    File dir = new File(folder, packageName.replace('.', '/'));
    if (!dir.exists()) {
      dir.mkdirs();
    }
    File output = new File(dir, name + ".java");
    if (!output.exists() || isOverride) {
      PrintWriter writer = null;
      try {
        writer = new PrintWriter(output);
        writer.print(print());
      } catch (FileNotFoundException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      } finally {
        writer.close();
      }
    }
    System.out.println("Writing...\t" + output.getAbsolutePath());
  }

  public void addAnnotation(Class annotationClass, String... values) {
    importClass(annotationClass);
    StringBuilder builder = new StringBuilder(annotationClass.getSimpleName());
    if (values.length > 0) {
      builder.append("(\"");
      for (String value : values) {
        builder.append(value);
      }
      builder.append("\")");
    }
    _annotations.add(builder.toString());
  }

  public void addToStringMethod() {
    JavaMethod toStringMethod = new ToStringMethod(this);
    _methods.add(toStringMethod);
  }

  public JavaClass asEnum() {
    _isEnum = true;
    _identifier = "enum";
    return this;
  }

  public JavaClass asInterface() {
    _isInterface = true;
    _identifier = "interface";
    return this;
  }

  public JavaMethod addGetMethod(JavaField field) {
    JavaMethod method = new GetMethod(this, field);
    _methods.add(method);
    return method;
  }

  public void addSetMethod(JavaField field) {
    JavaMethod method = new SetMethod(this, field);
    _methods.add(method);
  }

  public static String capitalize(String value) {
    return new StringBuilder()
            .append(Character.toUpperCase(value.charAt(0)))
            .append(value.substring(1)).toString();
  }

  public JavaClass withAnnotation(Class annotation) {
    importClass(annotation);
    _annotations.add(annotation.getSimpleName());
    return this;
  }

  public JavaMethod addMethod() {
    JavaMethod javaMethod = new JavaMethod(this);
    _methods.add(javaMethod);
    return javaMethod;
  }

  public JavaClass withMappingInfo(Object... info) {
    this.info = info;
    return this;
  }

  public void addMetaModel() {
    int length = fields.size();
    for (int i = 0; i < length; i++) {
      JavaField f = fields.get(i);
      addField().withName(f.name.toUpperCase() + "_PROP")
              .withModifier("public static final")
              .withType(String.class).withValue("\"" + f.name + "\"");
    }
  }

  public JavaClass withModifier(String modifier) {
    this.modifier = modifier;
    return this;
  }

  public String getFullName() {
    return packageName + "." + name;
  }
}
